/**
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.cas.server.ticket.registry.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.jasig.cas.logout.LogoutManager;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.registry.RegistryCleaner;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.ticket.registry.support.LockingStrategy;
import org.jasig.cas.ticket.registry.support.NoOpLockingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.NotNull;

import org.qloudgen.sec.cas.server.ticket.registry.RedisClusterCachedTicketRegistry;

/**
 * <p>Title: Cached Tickets Registry Cleaner</p>
 * <p>Description: Wrapper redis cluster cached tickets clean implementation.</p>
 * <p>Copyright: qloudgen/sec 2014</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public final class RedisClusterTicketRegistryCleaner implements RegistryCleaner {

    /** The Commons Logging instance. */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** The instance of the TicketRegistry to clean. */
    @NotNull
    private RedisClusterCachedTicketRegistry ticketRegistry;

    /** Execution locking strategy. */
    @NotNull
    private LockingStrategy lock = new NoOpLockingStrategy();

    /** The logout manager. */
    @NotNull
    private LogoutManager logoutManager;

    /** If the user must be logged out of the services. */
    private boolean logUserOutOfServices = true;
    
    private long count = 100;
    
    public void setCount(final long count) {
        this.count = count;
    }

    /**
     * @see org.jasig.cas.ticket.registry.RegistryCleaner#clean()
     */
    public void clean() {
        logger.info("Beginning ticket cleanup.");
        logger.debug("Attempting to acquire ticket cleanup lock.");
        if (!this.lock.acquire()) {
            logger.info("Could not obtain lock.  Aborting cleanup.");
            return;
        }
        logger.debug("Acquired lock.  Proceeding with cleanup.");
        try {
            //final List<Ticket> ticketsToRemove = new ArrayList<Ticket>();
            //final Collection<Ticket> ticketsInCache;
            //ticketsInCache = this.ticketRegistry.getTickets();
            //for (final Ticket ticket : ticketsInCache) {
            //    if (ticket.isExpired()) {
            //        ticketsToRemove.add(ticket);
            //    }
            //}
            
            int n = 0;
	    	int c = 0;
	    	while ( true ) {
		    	List<String> res = ticketRegistry.getTickets( n , c , "*" , count );
		    	if ( res == null ) {
		    		break;
		    	}
		    	//
		    	c = Integer.parseInt( res.get( 0 ) );
		    	logger.error( "Current ticket cleaner status...[{},{},{}]" , n , c , res.size() - 1 );
		    	//
		    	if ( res.size() > 1 ) {
			        final List<Ticket> ticketsToRemove = new ArrayList<Ticket>();
			        /**
			         * Add current scan result.
			         */
			        for ( int i = 1 ; i < res.size() ; i++ ) {
			        	final Ticket t = ticketRegistry.getTicket( res.get( i ) );
			        	if ( t != null && t.isExpired() ) {
			        		logger.error( "Current ticket cleaner found expired ticket...{}" , res.get( i ) );
			        		ticketsToRemove.add( t );
			        	}
			        }
			        /**
			         * Remove
			         */
			        logger.error( "Current ticket cleaner expired tickets will be removed...{}" , ticketsToRemove.size() );
		            for (final Ticket t : ticketsToRemove) {
		                // CAS-686: Expire TGT to trigger single sign-out
		                if ( logUserOutOfServices && t instanceof TicketGrantingTicket ) {
		                    logoutManager.performLogout( ( TicketGrantingTicket )t );
		                }
		                ticketRegistry.deleteTicket( t.getId() );
			    	}
			    }
			    //
		    	if ( c == 0 ) {
		    		n = n + 1;
		    	}
	    	}

            //logger.info("{} tickets found to be removed.", ticketsToRemove.size());
            //for (final Ticket ticket : ticketsToRemove) {
            //    // CAS-686: Expire TGT to trigger single sign-out
            //    if (this.logUserOutOfServices && ticket instanceof TicketGrantingTicket) {
            //        logoutManager.performLogout((TicketGrantingTicket) ticket);
            //    }
            //    this.ticketRegistry.deleteTicket(ticket.getId());
            //}
        } finally {
            logger.debug("Releasing ticket cleanup lock.");
            this.lock.release();
        }

        logger.info("Finished ticket cleanup.");
    }

    /**
     * @param ticketRegistry The ticketRegistry to set.
     */
    public void setTicketRegistry(final RedisClusterCachedTicketRegistry ticketRegistry) {
        this.ticketRegistry = ticketRegistry;
    }

    /**
     * @param  strategy  Ticket cleanup locking strategy.  An exclusive locking
     * strategy is preferable if not required for some ticket backing stores,
     * such as JPA, in a clustered CAS environment.  Use {@link JdbcLockingStrategy}
     * for {@link org.jasig.cas.ticket.registry.JpaTicketRegistry} in a clustered
     * CAS environment.
     */
    public void setLock(final LockingStrategy strategy) {
        this.lock = strategy;
    }

    /**
     * Whether to logger users out of services when we remove an expired ticket.  The default is true. Set this to
     * false to disable.
     *
     * @param logUserOutOfServices whether to logger the user out of services or not.
     */
    public void setLogUserOutOfServices(final boolean logUserOutOfServices) {
        this.logUserOutOfServices = logUserOutOfServices;
    }

    /**
     * Set the logout manager.
     *
     * @param logoutManager the logout manager.
     */
    public void setLogoutManager(final LogoutManager logoutManager) {
        this.logoutManager = logoutManager;
    }
}